package com.thread.demo.threadPool;

import org.junit.Test;

import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * Create by xcb on 11:35 2022/3/2
 */
public class ThreadPoolFactory {

    public static class ThreadPoolFactoryTest implements ThreadFactory {

        private AtomicInteger threadIndex = new AtomicInteger(0);

        private String threadNamePrefix;

        public ThreadPoolFactoryTest(String prefix) {
            threadNamePrefix = prefix;
        }

        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setName(threadNamePrefix + "-xxljob-" + threadIndex.getAndIncrement());
            return thread;
        }
    }

    /**
     * 可缓存无界线程池测试
     * 当线程池中的线程空闲时间超过60s则会自动回收该线程，核心线程数为0
     * 当任务超过线程池的线程数则创建新线程。线程池的大小上限为Integer.MAX_VALUE，
     * 可看做是无限大。
     */
    @Test
    public void cachedThreadPoolTest() {
        ExecutorService executorService = Executors.newCachedThreadPool(new ThreadPoolFactoryTest("cachedThread"));
        for(int i = 0; i < 10; i++) {
            executorService.submit(() -> {
                System.out.println("cachedThreadPool");
                System.out.println(Thread.currentThread().getName());
            });
        }
    }

    /**
     * 创建固定线程数量的线程池测试
     * 创建一个固定大小的线程池，该方法可指定线程池的固定大小，对于超出的线程会在LinkedBlockingQueue队列中等待
     * 核心线程数可以指定，线程空闲时间为0
     */
    @Test
    public void fixedThreadPoolTest() {
        ExecutorService executorService = Executors.newFixedThreadPool(5, new ThreadPoolFactoryTest("fixedThreadPool"));
        for (int i = 0; i < 10; i++) {
            executorService.submit(() -> {
                System.out.println("fixedThreadPool");
                        System.out.println(Thread.currentThread().getName());
                    }
            );
        }
    }

    /**
     * 创建定时周期执行的线程池测试
     *
     * schedule(Runnable command, long delay, TimeUnit unit)，延迟一定时间后执行Runnable任务；
     * schedule(Callable callable, long delay, TimeUnit unit)，延迟一定时间后执行Callable任务；
     * scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)，延迟一定时间后，以间隔period时间的频率周期性地执行任务；
     * scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit)，与scheduleAtFixedRate()方法很类似，
     * 但是不同的是scheduleWithFixedDelay()方法的周期时间间隔是以上一个任务执行结束到下一个任务开始执行的间隔，而scheduleAtFixedRate()方法的周期时间间隔是以上一个任务开始执行到下一个任务开始执行的间隔，
     * 也就是这一些任务系列的触发时间都是可预知的。
     * ScheduledExecutorService功能强大，对于定时执行的任务，建议多采用该方法。
     *
     * 作者：张老梦
     * 链接：https://www.jianshu.com/p/9ce35af9100e
     * 来源：简书
     * 著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
     */
    @Test
    public void scheduleThreadPoolTest() {
        // 创建指定核心线程数，但最大线程数是Integer.MAX_VALUE的可定时执行或周期执行任务的线程池
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5, new ThreadPoolFactoryTest("scheduledThread"));

        // 定时执行一次的任务，延迟1s后执行
        executorService.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("scheduleThreadPool");
                System.out.println(Thread.currentThread().getName() + ", delay 1s");
            }
        }, 1, TimeUnit.SECONDS);


        // 周期性地执行任务，延迟2s后，每3s一次地周期性执行任务
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + ", every 3s");
            }
        }, 2, 3, TimeUnit.SECONDS);


        executorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                long start = System.currentTimeMillis();
                System.out.println("scheduleWithFixedDelay 开始执行时间:" +
                        DateFormat.getTimeInstance().format(new Date()));
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                long end = System.currentTimeMillis();
                System.out.println("scheduleWithFixedDelay执行花费时间=" + (end - start) / 1000 + "m");
                System.out.println("scheduleWithFixedDelay执行完成时间："
                        + DateFormat.getTimeInstance().format(new Date()));
                System.out.println("======================================");
            }
        }, 1, 2, TimeUnit.SECONDS);

    }

    /**
     * 创建只有一个线程的线程池测试
     * 该方法无参数，所有任务都保存队列LinkedBlockingQueue中，核心线程数为1，线程空闲时间为0
     * 等待唯一的单线程来执行任务，并保证所有任务按照指定顺序(FIFO或优先级)执行
     */
    @Test
    public void singleThreadPoolTest() {
        // 创建仅有单个线程的线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadPoolFactoryTest("singleThreadPool"));
        for (int i = 0; i < 10; i++) {
            executorService.submit(() -> {
                System.out.println("singleThreadPool");
                System.out.println(Thread.currentThread().getName());
            });
        }
    }

    @Test
    public void workStealingPoolTest() throws InterruptedException {
        System.out.println("获得JAVA虚拟机可用的最大CPU处理器数量：" + Runtime.getRuntime().availableProcessors());
        ExecutorService executorService = Executors.newWorkStealingPool();
        /**
         * call方法存在返回值futureTask的get方法可以获取这个返回值。
         * 使用此种方法实现线程的好处是当你创建的任务的结果不是立即就要时，
         * 你可以提交一个线程在后台执行，而你的程序仍可以正常运行下去，
         * 在需要执行结果时使用futureTask去获取即可。
         */
        List<Callable<String>> callableList = IntStream.range(0, 20).boxed().map(i -> (Callable<String>) () -> {
            TimeUnit.SECONDS.sleep(3);
            System.out.println(String.format("当前【%s】线程正在执行>>>", Thread.currentThread().getName()));
            return "callable type thread task：" + i;
        }).collect(Collectors.toList());

        // 执行给定的任务，返回持有他们的状态和结果的所有完成的期待列表。
        executorService.invokeAll(callableList).stream().map(futureTask-> {
            try {
                return futureTask.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            return null;
        }).forEach(System.out::println);
    }


}
